require 'rubygems'
require_gem 'postgres'

class Database
  attr_reader :conn

  def initialize
    @conn = PGconn.connect('localhost', 5432, '', '', 
      'reality_development', 'postgres', '240374')
  end

  def query(sql)
    Query.new(@conn, sql)
  end
  
  def select(opts)
    return Query.new(@conn, opts[:sql]) if opts[:sql]

    if opts[:select]
      fields = opts[:select].is_a?(Array) ? opts[:select].join(',') : opts[:select]
    else
      fields = '*'
    end
    sql = "SELECT #{fields} FROM #{opts[:table]}"
    sql << " WHERE #{opts[:where]}" if opts[:where]
    Query.new(@conn, sql)
  end
  
  def select_count(opts)
    select(opts.merge(:select => 'COUNT(*)')).each {|r| return r[:count]}
  end
  
  def literal_repr(o)
    if String === o
      "'#{o}'"
    else
      o.to_s
    end
  end  

  def [](name)
    Filter.new(self, {:table => name})
  end
end

PG_TYPES = {
  16 => :to_bool,
  20 => :to_i,
  21 => :to_i,
  22 => :to_i,
  23 => :to_i,
  700 => :to_f,
  701 => :to_f
}

require 'metaid'

class Query
  attr_reader :result, :fields
  
  def initialize(conn, sql)
    @conn = conn
    @sql = sql
  end
  
  def execute(*args)
    @result = @conn.exec(@sql)
    @fields = @result.fields.map {|s| s.to_sym}
    @types = (0..(@result.num_fields - 1)).map {|idx| @result.type(idx)}
    compile_row_fetcher
  end
  
  def compile_row_fetcher
    parts = (0..(@result.num_fields - 1)).inject([]) do |m, f|
      translate_fn = PG_TYPES[@types[f]]
      translator = translate_fn ? ".#{translate_fn}" : ""
      m << ":#{@fields[f]} => r[#{f}]#{translator}"
    end
    l = eval("lambda {|r|{#{parts.join(',')}}}")
    extend(Module.new {define_method(:fetch_row, &l)})
  end
  
  def each(&block)
    execute
    @result.each do |r|
      block.call(fetch_row(r))
    end
    self
  end
  
  def row_count
    @result.num_tuples
  end
end

class Filter
  def initialize(db, opts)
    @db = db
    @opts = opts 
  end
  
  def compile_conditions(conditions)
    return nil unless conditions
    
    result = conditions.shift
    while result && (result =~ /\?/)
      a = @db.literal_repr(conditions.shift)
      result.sub!(/\?/, a)
    end
    result
  end
  
  def filter(conditions)
    Filter.new(@db, @opts.merge(:where => compile_conditions(conditions)))
  end
  
  alias_method :select, :filter
  
  def first(opts = nil)
    opts ? (opts[:limit] = 1) : (opts = {:limit => 1})
    each {|r| return r}
  end
  
  def[](attributes)
    params = []
    rule = attributes.inject('') {|m, kv| params << kv[1]; m << "(#{kv[0]} = ?)"}
    filter(params.unshift(rule)).first
  end
  
  def all(opts = nil)
    a = []
    each(opts) {|r| a << r}
    a
  end
  
  def each(opts = nil, &block)
    @db.select(opts ? @opts.merge(opts) : @opts).each(&block)
  end
  
  def count(opts = nil)
    @db.select_count(opts ? @opts.merge(opts) : @opts)
  end
  
  def summary(*fields)
    @db.select(@opts.merge(:select => fields)).each {|r| return r}
  end
  
  def update(values)
    @db.update(@opts, values)
  end
end

class Symbol
  def max
    "max(#{to_s}) AS max_#{to_s}"
  end

  def min
    "min(#{to_s}) AS min_#{to_s}"
  end
end

################################################################################
################################################################################

$db = Database.new
$t = $db[:states]

#puts $db[:nodes].all.inspect

puts $t.summary(:quality.max, :quality.min, :stamp.max, :stamp.min).inspect

################################################################################
# Table creation
################################################################################
#$db.create_table(:states, 
#  :fields => {
#    id => :int, 
#    :node_id => :int, 
#    :quality => :int, 
#    :stamp => :double,
#    :value => :text, 
#    :datatype => :int},
#  :index => :id
#);

################################################################################
# Joins
################################################################################

#
#
#
#
#
#
#
#
#
#

require 'benchmark'

def t1
  $t.each {|n| n[:node_id]}
end

def t2
  $t.filter(nil).each() {|n| n.inspect}
end

n = 100
Benchmark.bm do |x|
  x.report {n.times {t1}}  
  x.report {n.times {t2}}  
end
